#include <Bust/Crypting/SHA1.hpp>

#include <cstdio>

namespace bust {

const char* SHA1::engineName() {
	return "SHA1";
}

static void endianTest( int *endian_ness ) {
	if( ( *(unsigned short *) ( "#S" ) >> 8 ) == '#' ) {
		*endian_ness = ! ( 0 );
	} else {
		*endian_ness = 0;
	}
}

#define SHS_DATASIZE    64
#define SHS_DIGESTSIZE  20

#define f1(x,y,z)   ( z ^ ( x & ( y ^ z ) ) )
#define f2(x,y,z)   ( x ^ y ^ z )

#define f3(x,y,z)   ( ( x & y ) | ( z & ( x | y ) ) )
#define f4(x,y,z)   ( x ^ y ^ z )

#define K1  0x5A827999L
#define K2  0x6ED9EBA1L
#define K3  0x8F1BBCDCL
#define K4  0xCA62C1D6L

#define h0init  0x67452301L
#define h1init  0xEFCDAB89L
#define h2init  0x98BADCFEL
#define h3init  0x10325476L
#define h4init  0xC3D2E1F0L

#define ROTL(n,X)  ( ( ( X ) << n ) | ( ( X ) >> ( 32 - n ) ) )

#define expand(W,i) ( W[ i & 15 ] = ROTL( 1, ( W[ i & 15 ] ^ W[ (i - 14) & 15 ] ^ \
                                                 W[ (i - 8) & 15 ] ^ W[ (i - 3) & 15 ] ) ) )

#define subRound(a, b, c, d, e, f, k, data) \
    ( e += ROTL( 5, a ) + f( b, c, d ) + k + data, b = ROTL( 30, b ) )

SHA1::SHA1() {
}

SHA1::~SHA1() {
}

std::string SHA1::getHashFromString( std::string text ) {
	SHA1data ctx;

	init( ctx );
	update( ctx, (unsigned char*) text.c_str(), text.length() );

	unsigned char buff[SHS_DIGESTSIZE] = "";
	final( (unsigned char*) buff, ctx );

	return convToString( buff );
}

std::string SHA1::getHashFromFile( std::string filename ) {
	FILE *file;
	SHA1data context;

	int len;
	unsigned char buffer[1024], digest[SHS_DIGESTSIZE];

	if( ( file = fopen( filename.c_str(), "rb" ) ) == NULL ) {
		return "-1";
	}

	init( context );

	while( ( len = fread( buffer, 1, 1024, file ) ) ) {
		update( context, buffer, len );
	}
	final( digest, context );
	fclose( file );
	return convToString( digest );
}

void SHA1::init( SHA1data &context ) {
	endianTest( &context.Endianness );
	context.state[0] = h0init;
	context.state[1] = h1init;
	context.state[2] = h2init;
	context.state[3] = h3init;
	context.state[4] = h4init;

	context.count[0] = context.count[1] = 0;
}

void SHA1::transform( unsigned long int *digest, unsigned long int *data ) {
	unsigned long int A, B, C, D, E;
	unsigned long int eData[16];

	A = digest[0];
	B = digest[1];
	C = digest[2];
	D = digest[3];
	E = digest[4];
	SHA1_memcpy( (POINTER) eData, (POINTER) data, SHS_DATASIZE );

	subRound( A, B, C, D, E, f1, K1, eData[ 0 ] );
	subRound( E, A, B, C, D, f1, K1, eData[ 1 ] );
	subRound( D, E, A, B, C, f1, K1, eData[ 2 ] );
	subRound( C, D, E, A, B, f1, K1, eData[ 3 ] );
	subRound( B, C, D, E, A, f1, K1, eData[ 4 ] );
	subRound( A, B, C, D, E, f1, K1, eData[ 5 ] );
	subRound( E, A, B, C, D, f1, K1, eData[ 6 ] );
	subRound( D, E, A, B, C, f1, K1, eData[ 7 ] );
	subRound( C, D, E, A, B, f1, K1, eData[ 8 ] );
	subRound( B, C, D, E, A, f1, K1, eData[ 9 ] );
	subRound( A, B, C, D, E, f1, K1, eData[ 10 ] );
	subRound( E, A, B, C, D, f1, K1, eData[ 11 ] );
	subRound( D, E, A, B, C, f1, K1, eData[ 12 ] );
	subRound( C, D, E, A, B, f1, K1, eData[ 13 ] );
	subRound( B, C, D, E, A, f1, K1, eData[ 14 ] );
	subRound( A, B, C, D, E, f1, K1, eData[ 15 ] );
	subRound( E, A, B, C, D, f1, K1, expand( eData, 16 ) );
	subRound( D, E, A, B, C, f1, K1, expand( eData, 17 ) );
	subRound( C, D, E, A, B, f1, K1, expand( eData, 18 ) );
	subRound( B, C, D, E, A, f1, K1, expand( eData, 19 ) );

	subRound( A, B, C, D, E, f2, K2, expand( eData, 20 ) );
	subRound( E, A, B, C, D, f2, K2, expand( eData, 21 ) );
	subRound( D, E, A, B, C, f2, K2, expand( eData, 22 ) );
	subRound( C, D, E, A, B, f2, K2, expand( eData, 23 ) );
	subRound( B, C, D, E, A, f2, K2, expand( eData, 24 ) );
	subRound( A, B, C, D, E, f2, K2, expand( eData, 25 ) );
	subRound( E, A, B, C, D, f2, K2, expand( eData, 26 ) );
	subRound( D, E, A, B, C, f2, K2, expand( eData, 27 ) );
	subRound( C, D, E, A, B, f2, K2, expand( eData, 28 ) );
	subRound( B, C, D, E, A, f2, K2, expand( eData, 29 ) );
	subRound( A, B, C, D, E, f2, K2, expand( eData, 30 ) );
	subRound( E, A, B, C, D, f2, K2, expand( eData, 31 ) );
	subRound( D, E, A, B, C, f2, K2, expand( eData, 32 ) );
	subRound( C, D, E, A, B, f2, K2, expand( eData, 33 ) );
	subRound( B, C, D, E, A, f2, K2, expand( eData, 34 ) );
	subRound( A, B, C, D, E, f2, K2, expand( eData, 35 ) );
	subRound( E, A, B, C, D, f2, K2, expand( eData, 36 ) );
	subRound( D, E, A, B, C, f2, K2, expand( eData, 37 ) );
	subRound( C, D, E, A, B, f2, K2, expand( eData, 38 ) );
	subRound( B, C, D, E, A, f2, K2, expand( eData, 39 ) );

	subRound( A, B, C, D, E, f3, K3, expand( eData, 40 ) );
	subRound( E, A, B, C, D, f3, K3, expand( eData, 41 ) );
	subRound( D, E, A, B, C, f3, K3, expand( eData, 42 ) );
	subRound( C, D, E, A, B, f3, K3, expand( eData, 43 ) );
	subRound( B, C, D, E, A, f3, K3, expand( eData, 44 ) );
	subRound( A, B, C, D, E, f3, K3, expand( eData, 45 ) );
	subRound( E, A, B, C, D, f3, K3, expand( eData, 46 ) );
	subRound( D, E, A, B, C, f3, K3, expand( eData, 47 ) );
	subRound( C, D, E, A, B, f3, K3, expand( eData, 48 ) );
	subRound( B, C, D, E, A, f3, K3, expand( eData, 49 ) );
	subRound( A, B, C, D, E, f3, K3, expand( eData, 50 ) );
	subRound( E, A, B, C, D, f3, K3, expand( eData, 51 ) );
	subRound( D, E, A, B, C, f3, K3, expand( eData, 52 ) );
	subRound( C, D, E, A, B, f3, K3, expand( eData, 53 ) );
	subRound( B, C, D, E, A, f3, K3, expand( eData, 54 ) );
	subRound( A, B, C, D, E, f3, K3, expand( eData, 55 ) );
	subRound( E, A, B, C, D, f3, K3, expand( eData, 56 ) );
	subRound( D, E, A, B, C, f3, K3, expand( eData, 57 ) );
	subRound( C, D, E, A, B, f3, K3, expand( eData, 58 ) );
	subRound( B, C, D, E, A, f3, K3, expand( eData, 59 ) );

	subRound( A, B, C, D, E, f4, K4, expand( eData, 60 ) );
	subRound( E, A, B, C, D, f4, K4, expand( eData, 61 ) );
	subRound( D, E, A, B, C, f4, K4, expand( eData, 62 ) );
	subRound( C, D, E, A, B, f4, K4, expand( eData, 63 ) );
	subRound( B, C, D, E, A, f4, K4, expand( eData, 64 ) );
	subRound( A, B, C, D, E, f4, K4, expand( eData, 65 ) );
	subRound( E, A, B, C, D, f4, K4, expand( eData, 66 ) );
	subRound( D, E, A, B, C, f4, K4, expand( eData, 67 ) );
	subRound( C, D, E, A, B, f4, K4, expand( eData, 68 ) );
	subRound( B, C, D, E, A, f4, K4, expand( eData, 69 ) );
	subRound( A, B, C, D, E, f4, K4, expand( eData, 70 ) );
	subRound( E, A, B, C, D, f4, K4, expand( eData, 71 ) );
	subRound( D, E, A, B, C, f4, K4, expand( eData, 72 ) );
	subRound( C, D, E, A, B, f4, K4, expand( eData, 73 ) );
	subRound( B, C, D, E, A, f4, K4, expand( eData, 74 ) );
	subRound( A, B, C, D, E, f4, K4, expand( eData, 75 ) );
	subRound( E, A, B, C, D, f4, K4, expand( eData, 76 ) );
	subRound( D, E, A, B, C, f4, K4, expand( eData, 77 ) );
	subRound( C, D, E, A, B, f4, K4, expand( eData, 78 ) );
	subRound( B, C, D, E, A, f4, K4, expand( eData, 79 ) );

	digest[0] += A;
	digest[1] += B;
	digest[2] += C;
	digest[3] += D;
	digest[4] += E;
}

static void longReverse( unsigned long int *buffer, int byteCount,
		int Endianness ) {
	unsigned long int value;

	if( Endianness == true )
		return;
	byteCount /= sizeof(unsigned long int);
	while( byteCount-- ) {
		value = *buffer;
		value = ( ( value & 0xFF00FF00L ) >> 8 ) | ( ( value & 0x00FF00FFL )
				<< 8 );
		*buffer++ = ( value << 16 ) | ( value >> 16 );
	}
}

void SHA1::update( SHA1data &context, unsigned char *buffer, int count ) {
	unsigned long int tmp;
	int dataCount;

	/* Update bitcount */
	tmp = context.count[0];
	if( ( context.count[0] = tmp + ( (unsigned long int) count << 3 ) ) < tmp )
		context.count[1]++; /* Carry from low to high */
	context.count[1] += count >> 29;

	dataCount = (int) ( tmp >> 3 ) & 0x3F;

	if( dataCount ) {
		unsigned char *p = (unsigned char *) context.data + dataCount;

		dataCount = SHS_DATASIZE - dataCount;
		if( count < dataCount ) {
			SHA1_memcpy( p, buffer, count );
			return;
		}
		SHA1_memcpy( p, buffer, dataCount );
		longReverse( context.data, SHS_DATASIZE, context.Endianness );
		transform( context.state, context.data );
		buffer += dataCount;
		count -= dataCount;
	}

	while( count >= SHS_DATASIZE ) {
		SHA1_memcpy( (POINTER) context.data, (POINTER) buffer, SHS_DATASIZE );
		longReverse( context.data, SHS_DATASIZE, context.Endianness );
		transform( context.state, context.data );
		buffer += SHS_DATASIZE;
		count -= SHS_DATASIZE;
	}

	SHA1_memcpy( (POINTER) context.data, (POINTER) buffer, count );
}

void SHA1::final( unsigned char *output, SHA1data &context ) {
	int count;
	unsigned char *dataPtr;

	count = (int) context.count[0];
	count = ( count >> 3 ) & 0x3F;

	dataPtr = (unsigned char *) context.data + count;
	*dataPtr++ = 0x80;

	count = SHS_DATASIZE - 1 - count;
	if( count < 8 ) {
		SHA1_memset( dataPtr, 0, count );
		longReverse( context.data, SHS_DATASIZE, context.Endianness );
		transform( context.state, context.data );
		SHA1_memset( (POINTER) context.data, 0, SHS_DATASIZE - 8 );
	} else
		SHA1_memset( dataPtr, 0, count - 8 );

	context.data[14] = context.count[1];
	context.data[15] = context.count[0];

	longReverse( context.data, SHS_DATASIZE - 8, context.Endianness );
	transform( context.state, context.data );

	encode( output, context.state, SHS_DIGESTSIZE );

	SHA1_memset( (POINTER) &context, 0, sizeof ( context ) );
}

void SHA1::encode( unsigned char *output, unsigned long int *input,
		unsigned int len ) {
	unsigned int i, j;
	for( i = 0, j = 0; j < len; i++, j += 4 ) {
		output[j + 3] = (unsigned char) ( input[i] & 0xff );
		output[j + 2] = (unsigned char) ( ( input[i] >> 8 ) & 0xff );
		output[j + 1] = (unsigned char) ( ( input[i] >> 16 ) & 0xff );
		output[j] = (unsigned char) ( ( input[i] >> 24 ) & 0xff );
	}
}

void SHA1::SHA1_memcpy( POINTER output, POINTER input, unsigned int len ) {
	unsigned int i;

	for( i = 0; i < len; i++ )
		output[i] = input[i];
}

void SHA1::SHA1_memset( POINTER output, int value, unsigned int len ) {
	unsigned int i;
	for( i = 0; i < len; i++ )
		( (char *) output )[i] = (char) value;
}

std::string SHA1::convToString( unsigned char *bytes ) {
	char asciihash[41];

	int p = 0;
	for( int i = 0; i < 20; i++ ) {
		sprintf( &asciihash[p], "%02x", bytes[i] );
		p += 2;
	}
	asciihash[40] = '\0';
	return std::string( asciihash );
}

}
